<?php
/**
 * @author: justwkj
 * @date: 2021/8/6 10:13
 * @email: justwkj@gmail.com
 * @desc:
 *  微信支付文档 https://pay.weixin.qq.com/wiki/doc/api/index.html
 * h5 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1
 * jsspi  https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
 *
 *
 *  商户平台设置支付目录:
 *   1. 登录微信支付商户平台（pay.weixin.qq.com） https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/ico-guide/chapter1_2.shtml
 *          - 商户提供商户号 MCH_ID
 *          - 商户平台设置API秘钥 MCH_SECRET
 *          - 设置支付目录 产品中心-->开发配置，设置后一般5分钟内生效=,直接设置根域名即可 https://www.weixin.com/
 *          - 产品中心-配置服务号
 *
 *   2. JSAPI支付: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
 *          - 需要服务号的appid和app_secret WECHAT_APP_ID WECHAT_APP_SECRET
 *          - 需要设置网页授权地址,(上传文件校验), 业务域名以及js接口安全域名一起设置
 *
 *  3. H5支付: https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1
 *          - 提醒：H5支付不建议在APP端使用，如需要在APP中使用微信支付，请接APP支付，文档详见微信支付开发文档。
 *          - h5支付需要商户后台申请, 申请入口：登录商户平台-->产品中心-->我的产品-->支付产品-->H5支
 *
 *
 */

namespace app\common\library;


class WechatPay {
    const OPEN_APP_ID = 'wxb00000001'; //微信appid,开放平台ID
    const MCH_ID = '161000000'; //商户id
    const MCH_SECRET = '32位秘钥'; //商户平台API秘钥
    const NOTICE_URL = "http://www.jsyuxie.cn/wap/pay_callback/handle";//微信回调地址

    const WECHAT_APP_ID = 'wxb00000001'; //公众号appid
    const WECHAT_APP_SECRET = '';//公众号秘钥


    private $body;
    private $out_trade_no;//订单标号
    private $total_fee;//支付金额单位分

    private $trade_type='MWEB'; //h5 MWEB,  jsapi JSAPI
    private $openid;



    /**
     * h5 支付
     * @param $tradeNo 订单号码
     * @param $money 支付金额
     * @param string $text 支付内容
     * @return false|mixed
     * @author: justwkj
     * @date: 2021/8/6 14:59
     */
    public function h5($tradeNo, $money, $text = '会员权益购买') {
        $this->trade_type ='MWEB';
        $this->out_trade_no = $tradeNo; //订单编号
        $this->total_fee = $money;//支付金额设置(分)
        $this->body = $text;//支付标题

        $result = $this->unifiedOrder($tradeNo, $money, $text);
        if (!empty($result['wechat']['mweb_url'])) {
            return $result['wechat']['mweb_url'];
        }
        return '';
    }

    /**
     * JSAPI支付
     * @param $tradeNo
     * @param $money
     * @param $openid
     * @param string $text
     * @return array
     * @author: justwkj
     * @date: 2021/8/26 22:50
     */
    public function jssapi($tradeNo, $money, $openid, $text = '会员权益购买') {
        $this->trade_type ='JSAPI';
        $this->openid =$openid;

        $this->out_trade_no = $tradeNo; //订单编号
        $this->total_fee = $money;//支付金额设置(分)
        $this->body = $text;//支付标题

        return $this->unifiedOrder();
    }

    //jsapi支付必须要获取用户openid
    public static function buildGetCodeUrl($redirectUrl){
        $urlObj = [];
        $urlObj["appid"] = self::WECHAT_APP_ID;
        $urlObj["redirect_uri"] = "$redirectUrl";
        $urlObj["response_type"] = "code";
        $urlObj["scope"] = "snsapi_base";
        $urlObj["state"] = "STATE"."#wechat_redirect";
        return 'https://open.weixin.qq.com/connect/oauth2/authorize?'.http_build_query($urlObj);
    }

    //jsapi支付必须要获取用户openid
    public static function getOpenId($code){
        $urlObj = [];
        $urlObj["appid"] = self::WECHAT_APP_ID;
        $urlObj["secret"] = self::WECHAT_APP_SECRET;
        $urlObj["code"] = $code;
        $urlObj["grant_type"] = "authorization_code";
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?'.http_build_query($urlObj);

        $res = self::curlGet($url);
        $resArr = json_decode($res,true);
        return !empty($resArr['openid'])?$resArr['openid']:'';
    }


    //统一支付
    private function unifiedOrder() {
        $return = $newPara = array();
        //生成预支付交易单的必选参数:
        //应用ID
        $newPara["appid"] = self::OPEN_APP_ID;
        //商户号
        $newPara["mch_id"] = self::MCH_ID;
        //设备号
        $newPara["device_info"] = "H5";
        //随机字符串,这里推荐使用函数生成
        $newPara["nonce_str"] = self::getNonceStr();
        //商品描述
        $newPara["body"] = $this->body;
        //商户订单号,这里是商户自己的内部的订单号
        $newPara["out_trade_no"] = $this->out_trade_no;
        //总金额
        $newPara["total_fee"] = $this->total_fee;
        //终端IP
        $newPara["spbill_create_ip"] = $_SERVER["REMOTE_ADDR"];
        //通知地址，注意，这里的url里面不要加参数
        $newPara["notify_url"] = self::NOTICE_URL;
        //交易类型
        //$newPara["trade_type"] = "MWEB"; //h5
        //$newPara["trade_type"] = "JSAPI"; //JSAPI
        $newPara["trade_type"] = $this->trade_type;
        if('JSAPI' == $this->trade_type){
            $newPara['openid'] = $this->openid;
        }

        //第一次签名
        $newPara["sign"] = self::getSign($newPara);
        //把数组转化成xml格式
        $xmlData = self::getWeChatXML($newPara);
        //利用PHP的CURL包，将数据传给微信统一下单接口，返回正常的prepay_id
        $get_data = self::sendPrePayCurl($xmlData);

        writeLog('统一下单', 'wxpay');
        writeLog($get_data, 'wxpay');
        //返回的结果进行判断。
        if ($get_data['return_code'] == "SUCCESS" && $get_data['result_code'] == "SUCCESS") {
            $wechatData = array(
                "appId" => $newPara['appid'],
                "timeStamp" => time(),
                "nonceStr" => self::getNonceStr(),
                "package" => 'prepay_id='. $get_data['prepay_id'],
                "signType" => 'MD5',
            );
            if('MWEB' == $this->trade_type){
                $wechatData['mweb_url'] = isset($get_data['mweb_url']) ? $get_data['mweb_url'] : ''; //h5需要添加返回;
            }


            //根据微信支付返回的结果进行二次签名,二次签名需要的参数是这6个参数
            $_sign = self::getSign($wechatData);
            $wechatData['sign'] = $_sign;
            $return['wechat'] = $wechatData;
            $return['check_code'] = $newPara["out_trade_no"];
            $return['message'] = "ok";
            //p($newPara, $wechatData);
        } else {
            $return['wechat'] = array();
            $return['check_code'] = $newPara["out_trade_no"];
            $return['message'] = $get_data['return_msg'];
        }
        writeLog('$return', 'wxpay');
        writeLog($return, 'wxpay');

        return $return;
    }

    /**
     *  获取随机字符串
     * @return string
     * @author:wkj
     * @date  2017/11/3 9:41
     */
    public static function getNonceStr() {
        return md5(uniqid() . microtime() . rand(0, 999999));
    }


    /**
     *  签名算法
     * @param $params
     * @return string
     * @author:wkj
     * @date  2017/11/3 9:41
     */
    public static function getSign($params) {
        unset($params['sign']);
        ksort($params);
        $stringA = urldecode(http_build_query($params));
        $stringSignTemp = "$stringA&key=" . self::MCH_SECRET;

        return strtoupper(md5($stringSignTemp));
    }


    /**
     *  微信接口调用
     * @param $xmlData
     * @return array
     * @author:wkj
     * @date  2017/11/3 9:41
     */
    public static function sendPrePayCurl($xmlData) {
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        $header[] = "Content-type: text/xml";

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $xmlData);
        $data = curl_exec($curl);
        if (curl_errno($curl)) {
            echo curl_error($curl);
            exit;
        }
        curl_close($curl);

        return self::XMLDataParse($data);

    }


    /**
     *  数组转化XML
     * @param $newPara
     * @return string
     * @author:wkj
     * @date  2017/11/3 9:41
     */
    public static function getWeChatXML($newPara) {
        $xmlData = "<xml>";
        foreach ($newPara as $key => $value) {
            $xmlData = $xmlData . "<" . $key . ">" . $value . "</" . $key . ">";
        }
        $xmlData = $xmlData . "</xml>";

        return $xmlData;
    }

    /**
     *  XML转化数组
     * @param $data
     * @return array
     * @author:wkj
     * @date  2017/11/3 9:42
     */
    public static function XMLDataParse($data) {
        return (array)simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA);
    }

    /**
     *  curl get
     * @param $url
     * @param array $params
     * @param array $headers
     * @param int $timeOut
     * @param bool $isFollow
     * @return bool|string
     * @author: justwkj
     * @date: 2019-09-16 13:48
     */
    public static function curlGet($url, $params = [], $headers = [], $timeOut = 10, $isFollow = false) {
        $paramsStr = '';
        if ($params) {
            if (strpos($url, '?') === false) {
                $paramsStr .= '?';
            } else {
                $paramsStr .= '&';
            }
            $paramsStr .= http_build_query($params);
        }

        $url = rtrim($url . $paramsStr, '&');
        $ch = curl_init();
        if (stripos($url, "https://") !== false) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_SSLVERSION, 1);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeOut);
        if ($isFollow) {
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        }
        if ($headers) {
            $_header = [];
            foreach ($headers as $key => $vo) {
                $_header[] = $key . ':' . $vo;
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $_header);
        }
        $sContent = curl_exec($ch);
        $aStatus = curl_getinfo($ch);
        curl_close($ch);

        if (intval($aStatus["http_code"]) == 200) {
            return $sContent;
        } else {
            return false;
        }
    }
}